package com.higgins.eventtracking.server;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.jdo.Query;

import com.higgins.eventtracking.client.FinanceTrackingService;
import com.higgins.eventtracking.client.SecurityAccessDeniedException;
import com.higgins.eventtracking.client.dto.ChartDataDTO;
import com.higgins.eventtracking.client.dto.DynamicTypeDTO;
import com.higgins.eventtracking.client.dto.FinanceStatsDTO;
import com.higgins.eventtracking.client.dto.FinanceGoalDTO;
import com.higgins.eventtracking.client.dto.TransactionDTO;
import com.higgins.eventtracking.server.dataModel.DynamicType;
import com.higgins.eventtracking.server.dataModel.Transaction;

public class FinanceTrackingDataService extends GenericDataStoreService	implements FinanceTrackingService	{
	private AssetTrackingDataService assetTrackingService = new AssetTrackingDataService();
	
	public Double retrieveTotalSpentThisWeek(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		checkAuthority();
		Date startOfWeek = DateUtility.getStartOfWeek(now);
		Date endOfWeek = DateUtility.getEndOfWeek(now);
		return retrieveTotalSpend(startOfWeek, endOfWeek, yearlyExpense);
	}
	public Double retrieveTotalSpentThisMonth(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		checkAuthority();
		Date startOfMonth = DateUtility.getStartOfMonth(now);
		Date endOfMonth = DateUtility.getEndOfMonth(now);
		return retrieveTotalSpend(startOfMonth, endOfMonth, yearlyExpense);
	}
	public Double retrieveForecastSpendThisWeek(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		Double totalSpentThisWeekEx = retrieveTotalSpentThisWeek(now, false);
		Double totalSpentThisWeekInc = retrieveTotalSpentThisWeek(now, true);
		Double yearlyExpenseAmount = totalSpentThisWeekInc - totalSpentThisWeekEx;
		
		Double percentageComplete = DateUtility.percentageWeekComplete(now);
		return (totalSpentThisWeekEx / percentageComplete) + (yearlyExpense ? yearlyExpenseAmount:0.0);
	}
	public Double retrieveForecastSpendThisMonth(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		Double totalSpentThisMonthEx = retrieveTotalSpentThisMonth(now, false);
		Double totalSpentThisMonthInc = retrieveTotalSpentThisMonth(now, true);
		Double yearlyExpenseAmount = totalSpentThisMonthInc - totalSpentThisMonthEx;
		
		Double percentageComplete = DateUtility.percentageMonthComplete(now);
		return (totalSpentThisMonthEx / percentageComplete) + (yearlyExpense ? yearlyExpenseAmount : 0.0);
	}
	public Double retrieveAverageWeeklySpend(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		Double forecastSpendThisMonth = retrieveForecastSpendThisMonth(now, false);
		Integer weeksInMonth = DateUtility.getWeeksInMonth(now);
		return forecastSpendThisMonth / weeksInMonth;
	}
	public Double retrieveYearlyExpensesThisMonth(Date now)	throws SecurityAccessDeniedException	{
		Double totalSpentThisMonthEx = retrieveTotalSpentThisMonth(now, false);
		Double totalSpentThisMonthInc = retrieveTotalSpentThisMonth(now, true);
		Double yearlyExpenseAmount = totalSpentThisMonthInc - totalSpentThisMonthEx;
		return yearlyExpenseAmount;
	}
	public Double retrieveForecastSavingsThisMonth(Date now, Boolean yearlyExpense) throws SecurityAccessDeniedException	{
		IncomeSettingsDataService incomeDataService = new IncomeSettingsDataService();
		incomeDataService.openPersistenceManager();
		try	{
			Double forecastSpendThisMonth = retrieveForecastSpendThisMonth(now, yearlyExpense);
			Double income = Double.parseDouble(incomeDataService.retrieveIncome());
			Double outgoing = Double.parseDouble(incomeDataService.retrieveOutgoing());
			Double severableOutgoing = Double.parseDouble(incomeDataService.retrieveSeverableOutgoing());
			return income - outgoing - severableOutgoing - forecastSpendThisMonth;
		} catch (Exception e)	{
			incomeDataService.closePersistenceManager();
		} finally	{
			incomeDataService.closePersistenceManager();
		}
		return null;
	}
	
	public ChartDataDTO retrieveFinanceSavingsData(Integer points)	{
		try {
			List<String> axisText = new ArrayList<String>();
			List<Double> data1 = new ArrayList<Double>();

			assetTrackingService.openPersistenceManager();
			
			Date endMonth = DateUtility.getEndOfMonth(new Date());
			for (int count = points; count > 0; count--)	{
				Date date = DateUtility.addMonths(endMonth, -count);
				date = DateUtility.addDays(date, -15);
				date = DateUtility.getEndOfMonth(date);
				date = DateUtility.addDays(date, 1);
				Double totalAssets = assetTrackingService.retrieveAssetAmount(null, date);
				int monthOfYear = DateUtility.getMonth(date);
				String month = DateUtility.getMonth((monthOfYear-count)<0?(12+monthOfYear-count):(monthOfYear-count));
				axisText.add(month);
				data1.add(totalAssets);
			}
			Double totalAssets = assetTrackingService.retrieveForecastedTotalAssets(true, new Date(), endMonth);
			int monthOfYear = DateUtility.getMonth(endMonth);
			String month = DateUtility.getMonth((monthOfYear));
			axisText.add(month);
			data1.add(totalAssets);
			
			ChartDataDTO chartData = new ChartDataDTO();
			chartData.setData(data1);
			chartData.setXAxis(axisText);
			return chartData;			
		} catch (SecurityAccessDeniedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally	{
			assetTrackingService.closePersistenceManager();
		}
		return null;
	}	
	public ChartDataDTO retrieveFinanceGoalsData(Integer points)	{
		try {
			List<String> axisText = new ArrayList<String>();
			List<Double> data1 = new ArrayList<Double>();
			List<Double> data2 = new ArrayList<Double>();

			assetTrackingService.openPersistenceManager();
			List<FinanceGoalDTO> goals = assetTrackingService.retrieveGoals();
//			Double totalAssets = assetTrackingService.retrieveAssetAmount(null);
			Double totalAssets = assetTrackingService.retrieveForecastedTotalAssets(true, new Date(), DateUtility.addMonths(new Date(), points));
			
			for (FinanceGoalDTO goal:goals)	{
				axisText.add(goal.getGoal());
				data1.add(goal.getPrice());
				if (goal.getPrice() > totalAssets)	{
					data2.add(totalAssets);
					totalAssets = 0.0;
				} else {
					data2.add(goal.getPrice());
					totalAssets = totalAssets - goal.getPrice();
				}
			}
			
			ChartDataDTO chartData = new ChartDataDTO();
			chartData.setData(data1);
			chartData.setDataTwo(data2);
			chartData.setXAxis(axisText);
			return chartData;			
		} catch (SecurityAccessDeniedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally	{
			assetTrackingService.closePersistenceManager();
		}
		return null;
	}
	public ChartDataDTO retrieveFinanceChartData(Boolean weeklyChart, Boolean yearlyExpense, Integer points) 	{
		if (weeklyChart)
			return getFinanceWeeklyData(points, yearlyExpense);
		else
			return getFinanceMonthlyData(points, yearlyExpense);
	}	
	public ChartDataDTO getFinanceWeeklyData(int numberOfDataPoints, Boolean yearlyExpense)	{
		Date now = new Date();
		int year = DateUtility.getYear(now);
		List<Double> data = new ArrayList<Double>();
		List<String> axisText = new ArrayList<String>();
		Double number = 0.0;
		int weekOfYear = DateUtility.getWeekNumberOfYear(now);
		for (int x=numberOfDataPoints-1; x>-1; x--)	{
			Date startDate = DateUtility.getStartOfWeek(weekOfYear-x, year);
			Date endDate = DateUtility.getEndOfWeek(weekOfYear-x, year);
			
			if (x==0)	{
				try {
					number = retrieveForecastSpendThisWeek(new Date(), yearlyExpense);
				} catch (SecurityAccessDeniedException e) {
					number = retrieveTotalSpend(startDate, endDate, yearlyExpense);
				}
			}
			else	{
				number = retrieveTotalSpend(startDate, endDate, yearlyExpense);
			}
			data.add(number);
			axisText.add("" + ((weekOfYear - x) + ((weekOfYear-x)<=0?52:0)));
		}
		ChartDataDTO chartData = new ChartDataDTO();
		chartData.setData(data);
		chartData.setXAxis(axisText);
		return chartData;
	}		
	public ChartDataDTO getFinanceMonthlyData(int numberOfDataPoints, Boolean yearlyExpense)	{
		Date now = new Date();
		int year = DateUtility.getYear(now);
		List<Double> data = new ArrayList<Double>();
		List<String> axisText = new ArrayList<String>();
		Double number = 0.0;
		int monthOfYear = DateUtility.getMonth(now);
		for (int x=numberOfDataPoints-1; x>-1; x--)	{
			Date startDate = DateUtility.getStartOfMonth((monthOfYear-x)<0?(12+monthOfYear-x):(monthOfYear-x), (monthOfYear-x)<0?(year-1):year);
			Date endDate = DateUtility.getEndOfMonth((monthOfYear-x)<0?(12+monthOfYear-x):(monthOfYear-x), (monthOfYear-x)<0?(year-1):year);
			
			if (x==0)	{
				try {
					number = retrieveForecastSpendThisMonth(new Date(), yearlyExpense);
				} catch (SecurityAccessDeniedException e) {
					number = retrieveTotalSpend(startDate, endDate, yearlyExpense);
				}
			}
			else	{
				number = retrieveTotalSpend(startDate, endDate, yearlyExpense);
			}
			data.add(number>100.0?(1710.0 - number):0);
			
			String month = DateUtility.getMonth((monthOfYear-x)<0?(12+monthOfYear-x):(monthOfYear-x));
			axisText.add(month);
			
		}
		ChartDataDTO chartData = new ChartDataDTO();
		chartData.setData(data);
		chartData.setXAxis(axisText);
		return chartData;
	}		
	
	@Override
	public ChartDataDTO retrieveFinanceBreakdownChartData(Boolean weeklyChart,
			Boolean yearlyExpense, Integer offset, Integer points) throws IllegalArgumentException {
		Date now = new Date();
		int year = DateUtility.getYear(now);
		List<Double> data = new ArrayList<Double>();
		List<String> axisText = new ArrayList<String>();
		int monthOfYear = DateUtility.getMonth(now);
		Date startDate = null;
		Date endDate = null;
		int x = offset-1;
		int y = x-(points-1);
		
		if (weeklyChart)	{
			int weekOfYear = DateUtility.getWeekNumberOfYear(now);
			startDate = DateUtility.getStartOfWeek((weekOfYear-x)<0?(52+weekOfYear-x):(weekOfYear-x), (weekOfYear-x)<0?(year-1):year);
			endDate = DateUtility.getEndOfWeek((weekOfYear-y)<0?(52+weekOfYear-y):(weekOfYear-y), (weekOfYear-y)<0?(year-1):year);
		} else {
			startDate = DateUtility.getStartOfMonth((monthOfYear-x)<0?(12+monthOfYear-x):(monthOfYear-x), (monthOfYear-x)<0?(year-1):year);
			endDate = DateUtility.getEndOfMonth((monthOfYear-y)<0?(12+monthOfYear-y):(monthOfYear-y), (monthOfYear-y)<0?(year-1):year);
		}
		
		List<String> listOfCategories = retrieveCategories();
		for (String category:listOfCategories)	{
			data.add(retrieveTotalSpend(startDate, endDate, yearlyExpense, category));
			axisText.add(category);
		}
			
		ChartDataDTO chartData = new ChartDataDTO();
		chartData.setData(data);
		chartData.setXAxis(axisText);
		return chartData;	}
	
	
	
	
	public Double getTotalExpensesThisMonthJustGone()	{
		try	{
			checkAuthority();
			return getTotalExpensesThisMonthJustGone(true);
		} catch (Exception e)	{
			return null;
		}
	}
	public Double getTotalExpensesThisMonthJustGone(Boolean auth)	{
		try	{
			Integer yearLastMonth = DateUtility.getYearLastMonth(new Date());
			Integer lastMonth = DateUtility.getLastMonth(new Date());
			
			Date startDate = DateUtility.getStartOfMonth(lastMonth, yearLastMonth);
			Date endDate = DateUtility.getEndOfMonth(lastMonth, yearLastMonth);
			return retrieveTotalSpend(startDate, endDate, false);
		} catch (Exception e)	{
			return null;
		}
	}
	
	@Deprecated
	public Double getTotalExpensesThisMonth()	{
		Double expectedSpendThisMonth = 0.0;
		try	{
			checkAuthority();
			Date now = new Date();
			Double averageWeeklySpendThisMonth = getAverageWeeklySpent();
			expectedSpendThisMonth = (averageWeeklySpendThisMonth * DateUtility.getWeeksInMonth(now));
		} catch (Exception e)	{
		}
		return expectedSpendThisMonth;
	}
	@Deprecated
	public Double getAverageWeeklySpent()	{
		Integer weekNumber = DateUtility.getWeekNumberOfMonth(new Date());
		if (weekNumber>1)	
			return getAverageWeeklySpentThisMonth();
		else 
			return getAverageWeeklySpentLastMonth();
	}
	@Deprecated
	public Double getAverageWeeklySpentThisMonth()	{
		return null;
	}
	@Deprecated
	public Double getAverageWeeklySpentLastMonth()	{
		return null;
	}
	
	
	@SuppressWarnings("unchecked")
	public Double retrieveTotalSpend(Date startDate, Date endDate, Boolean yearlyExpense)	{
		List<Transaction> results; 
		Query query;
		query=pm.newQuery(Transaction.class);
		query.setFilter("timestamp >= startDate && timestamp <= endDate ");
		query.declareParameters("java.util.Date startDate, java.util.Date endDate");
		query.setOrdering("timestamp desc");
		results = (List<Transaction>) query.execute(startDate, endDate);
		
		Double totalSpent = 0.0;
		Iterator<Transaction> iterator = results.iterator();
		while (iterator.hasNext())	{
			Transaction transaction = iterator.next();
			if (yearlyExpense)
				totalSpent += transaction.getAmount();
			else	{ 
				if (transaction.getYearlyExpense()==null)
					totalSpent += transaction.getAmount();
				else if (transaction.getYearlyExpense().equals(false))
					totalSpent += transaction.getAmount();
			}
		}
		
		return totalSpent;
	}
	public Double retrieveTotalSpend(Date startDate, Date endDate, Boolean yearlyExpense, String category)	{
		List<Transaction> results; 
		Query query;
		query=pm.newQuery(Transaction.class);
		query.setFilter("timestamp >= startDate && timestamp <= endDate && transactionType == category");
		query.declareParameters("java.util.Date startDate, java.util.Date endDate, String category");
		query.setOrdering("timestamp desc");
		results = (List<Transaction>) query.execute(startDate, endDate, category);
		
		Double totalSpent = 0.0;
		Iterator<Transaction> iterator = results.iterator();
		while (iterator.hasNext())	{
			Transaction transaction = iterator.next();
			if (yearlyExpense)
				totalSpent += transaction.getAmount();
			else	{ 
				if (transaction.getYearlyExpense()==null)
					totalSpent += transaction.getAmount();
				else if (transaction.getYearlyExpense().equals(false))
					totalSpent += transaction.getAmount();
			}
		}
		
		return totalSpent;
	}	
	@Override
	public String insertTransaction(TransactionDTO dto) throws IllegalArgumentException	{
		Transaction transaction = new Transaction(dto);
		Boolean success = true;
		try	{
			pm.makePersistent(transaction);
		} 
		catch (Exception e)	{
			success = false;
		}
		return success?"success":"unsuccessful";
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<TransactionDTO> retrieveTransactions(Date now,	Boolean weekOrMonth) {
		Date start, end;
		if (weekOrMonth)	{
			start = DateUtility.getStartOfWeek(now);
			end = DateUtility.getEndOfWeek(now);
		} else	{
			start = DateUtility.getStartOfMonth(now);
			end = DateUtility.getEndOfMonth(now);
		}
		return retrieveTransactions(start, end);
	}	
	
	public List<TransactionDTO> retrieveTransactions(Date start, Date end) {
		List<Transaction> results; 
		Query query;
		query=pm.newQuery(Transaction.class);
		query.setFilter("timestamp >= startDate && timestamp <= endDate ");
		query.declareParameters("java.util.Date startDate, java.util.Date endDate");
		query.setOrdering("timestamp asc");
		results = (List<Transaction>) query.execute(start, end);
		List<TransactionDTO> transactionDTOs = new ArrayList<TransactionDTO>();
		for (Transaction transaction:results)	{
			TransactionDTO transactionDTO = transaction.toDTO();
			transactionDTOs.add(transactionDTO);
		}
		return transactionDTOs;
	}		
	
	@Override
	public String retrieveWeekNumber(Date now) throws IllegalArgumentException,
			SecurityAccessDeniedException {
		// TODO Auto-generated method stub
		return null;
	}
	private List<String> retrieveCategories()	{
		List<String> results = new ArrayList<String>(); 
		try {
			List<DynamicTypeDTO> dtos = retrieveDynamicTypes("TransactionType");
			for (DynamicTypeDTO dto:dtos)	{
				results.add(dto.getDynamicType());
			}
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityAccessDeniedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
//		results.add("Weekly Shop");
//		results.add("Breakfast");
//		results.add("Lunch");		
//		results.add("Dinner");
//		results.add("Coffee");
//		results.add("Date");
//		results.add("Social");		
//		results.add("Petrol");		
//		results.add("Other");		
		return results;
	}
	@Override
	public List<TransactionDTO> retrieveTransactions(Integer weekInPast,
			Boolean weekOrMonth) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public String updateTransaction(TransactionDTO dto)
			throws IllegalArgumentException, SecurityAccessDeniedException {
		Transaction transaction = null;
		if (dto.getId()!=null)	{
			transaction = pm.getObjectById(Transaction.class, dto.getId());
		} else {
			transaction = new Transaction();
		}
		transaction.update(dto);
		pm.makePersistent(transaction);
		return transaction.getId().toString();
	}
	@Override
	public String deleteTransaction(Long id) throws IllegalArgumentException,
			SecurityAccessDeniedException {
		Transaction transaction = null;
		if (id!=null)	{
			transaction = pm.getObjectById(Transaction.class, id);
			pm.deletePersistent(transaction);
		}
		return transaction.getId().toString();
	}
	@Override
	public List<DynamicTypeDTO> retrieveDynamicTypes(String category)
			throws IllegalArgumentException, SecurityAccessDeniedException {
		List<DynamicType> results; 
		Query query;
		query=pm.newQuery(DynamicType.class);
		query.setFilter("category == categoryParam");
		query.declareParameters("String categoryParam");
//		query.setOrdering("timestamp asc");
		results = (List<DynamicType>) query.execute(category);
		
		Collections.sort(results, new Comparator<DynamicType>() {

			@Override
			public int compare(DynamicType arg0, DynamicType arg1) {
				// TODO Auto-generated method stub
				return arg0.getTimestamp().compareTo(arg1.getTimestamp());
			}
			
		});
		
		List<DynamicTypeDTO> dtos = new ArrayList<DynamicTypeDTO>();
		for (DynamicType type:results)	{
			DynamicTypeDTO dto = type.toDTO();
			dtos.add(dto);
		}
		return dtos;
	}
	@Override
	public String updateDynamicType(DynamicTypeDTO dto)
			throws IllegalArgumentException, SecurityAccessDeniedException {
		DynamicType type = null;
		if (dto.getId()!=null)	{
			type = pm.getObjectById(DynamicType.class, dto.getId());
		} else {
			type = new DynamicType();
		}
		type.update(dto);
		pm.makePersistent(type);
		return type.getId().toString();
	}

}
